//
// Created by QU on 24-6-21.
//

#include <iostream>
#include <functional>

/**
 * 虽然C++一直以来都没有支持lambda表达式，但是它对lambda表达式的需求却非常高。最明显的就是STL，在STL中有大量需
 * 要传入谓词的算法函数，比如std::find_if、std::replace_if等。过去有两种方法实现谓词函数：编写纯函数或者仿函数。但是它们的定
 * 义都无法直接应用到函数调用的实参中，面对复杂工程的代码，我们可能需要四处切换源文件来搜索这些函数或者仿函数。
 * 为了解决上面这些问题，C++11标准为我们提供了lambda表达式的支持，而且语法非常简单明了。
 */

// lambda表达式的语法非常简单，具体定义如下：
// [ captures ] ( params ) specifiers exception -> ret { body }

/*    [ captures ] —— 捕获列表，它可以捕获当前函数作用域的零个或多个变量，变量之间用逗号分隔。在对应的例子中，[x]是一
个捕获列表，不过它只捕获了当前函数作用域的一个变量x，在捕获了变量之后，我们可以在lambda表达式函数体内使用这个变量，比如
return x * y。另外，捕获列表的捕获方式有两种：按值捕获和引用捕获，下文会详细介绍。

  　　( params ) —— 可选参数列表，语法和普通函数的参数列表一样，在不需要参数的时候可以忽略参数列表。对应例子中的(int y)。

     specifiers —— 可选限定符，C++11中可以用mutable，它
允许我们在lambda表达式函数体内改变按值捕获的变量，或者调用非const的成员函数。上面的例子中没有使用说明符。

  　　exception —— 可选异常说明符，我们可以使用noexcept来指明lambda是否会抛出异常。对应的例子中没有使用异常说明符。

  　　ret —— 可选返回值类型，不同于普通函数，lambda表达式使用返回类型后置的语法来表示返回类型，如果没有返回值（void类
型），可以忽略包括->在内的整个部分。另外，我们也可以在有返回值的情况下不指定返回类型，这时编译器会为我们推导出一个返回类
型。对应到上面的例子是->int。{ body } —— lambda表达式的函数体，这个部分和普通函 数的函数体一样。
对应例子中的{ return x * y; }。细心的读者肯定发现了一个有趣的事实，由于参数列表，限定符以及返回值都是可选的，
于是我们可以写出的最简单的lambda表达式是[]{}。虽然看上去非常奇怪，但它确实是一个合法的lambda表达式。
需要特别强调的是，上面的语法定义只属于C++11标准，C++14和C++17标准对lambda表达式又进行了很有用的扩展，我们会在后面介绍。
*/

///***************************************************************************************************************///

// 捕获列表中的变量存在于两个作用域——lambda表达式定义的函数作用域以及lambda表达式函数体的作用域。
// 前者是为了捕获变量，后者是为了使用变量。另外，标准还规定能捕获的变量必须是一个自动存储类型。简单
// 来说就是非静态的局部变量。让我们看一看下面的例子：

/*
int x = 0;
int main()
{
    int y = 0;
    static int z = 0;
    auto foo = [x, y, z] {};
}
*/

// 以上代码可能是无法通过编译的，其原因有两点：第一，变量x和z不是自动存储类型的变量；第二，x不存在于lambda表达式定义的作
// 用域。这里可能无法编译，因为不同编译器对于这段代码的处理会有所不同，比如GCC就不会报错，而是给出警告。


// 那么如果想在lambda表达式中使用全局变量或者静态局部变量该怎么办呢？
// 马上能想到的办法是用参数列表传递全局变量或者静态局部变量，其实不必这么麻烦，**直接用就行了**，来看一看下面的代码：

/*
#include <iostream>
int x = 1;
int main()
{
    int y = 2;
    static int z = 3;
    auto foo = [y] { return x + y + z; };
    std::cout << foo() << std::endl;
}
*/

/// ************************************************************************************** ///

// 特殊的捕获方法
// lambda表达式的捕获列表除了指定捕获变量之外还有3种特殊的捕获方法。
// 1．[this] —— 捕获this指针，捕获this指针可以让我们使用this类型的成员变量和函数。
// 2．[=] —— 捕获lambda表达式定义作用域的全部变量的值，包括this。
// 3．[&] —— 捕获lambda表达式定义作用域的全部变量的引用，包括this。


int main(int argc, const char* argv[])
{
    int x = 5;
    auto foo_copy = [x](int y) mutable -> decltype(x * y) { x++; /* 没有mutable修改不了*/ return x * y; };
    auto foo_ref = [&x](int y) -> double { x = 6; return x * y; };

    std::cout << foo_copy(8) << std::endl;
    std::cout << foo_copy(8) << std::endl;
    std::cout << "x = " << x << std::endl << std::endl;

    std::cout << foo_ref(8) << std::endl;
    std::cout << "x = " << x << std::endl;
}
